/*
 * Copyright (c) 2003, Christian Edward Gruber
 * Copyright (c) 2003, Israfil Consulting Services Corporation
 * 
 * This software is licensed under the Berkeley Standard Distribution license,
 * (BSD license), as defined below:
 * 
 * Redistribution and use in source and binary forms, with or without 
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this 
 *    list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice, 
 *    this list of conditions and the following disclaimer in the documentation 
 *    and/or other materials provided with the distribution.
 * 3. Neither the name of Israfil Consulting Services nor the names of its contributors 
 *    may be used to endorse or promote products derived from this software without 
 *    specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
 * IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 
 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY 
 * OF SUCH DAMAGE.
 *
 * $Id: ReciprocalHashMap.java 129 2006-12-31 23:20:02Z cgruber $
 */
package net.israfil.foundation.collections;

import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

/** 
 * A two-way map, backed by java.util.HashMaps, where each key matches one 
 * value and is unique, and where each value maps to a key, and is unique 
 * among all values.
 * 
 * @author <a href="mailto:cgruber@israfil.net">Christian Edward Gruber</a>
 * 
 */
public class ReciprocalHashMap<K,V> extends java.util.AbstractMap<K,V>
		implements ReciprocalMap<K,V> {
		
	Set<Map.Entry<K,V>> entrySetByKey = new HashSet<Map.Entry<K,V>>();
	Set<Map.Entry<V,K>> entrySetByValue = new HashSet<Map.Entry<V,K>>();

	@Override
	public Set<Map.Entry<K,V>> entrySet() {
		return entrySetByKey;
	}
	
	public Set<Map.Entry<V,K>> entrySetByValue() {
		return entrySetByValue;
	}

	public boolean contains(K key, V value) {
		V value1 = get(key);
		if (value == value1) return true;
		else if (value == null) return false; // assume null == null handled.
		else return value.equals(value1);
	}

	public Entry<V,K> getEntryByValue(V value) {
    	Iterator<Entry<V,K>> i = entrySetByValue().iterator();
    	if (value==null) {
    	    while (i.hasNext()) {
    	    	Entry<V,K> e = i.next();
    			if (e.getKey()==null) return e;
    	    }
    	} else {
    	    while (i.hasNext()) {
    	    	Entry<V,K> e = i.next();
    			if (value.equals(e.getKey())) return e;
    	    }
    	}
    	return null;
	}
	public Entry<K,V> getEntryByKey(K value) {
    	Iterator<Entry<K,V>> i = entrySet().iterator();
    	if (value==null) {
    	    while (i.hasNext()) {
    	    	Entry<K,V> e = i.next();
    			if (e.getKey()==null) return e;
    	    }
    	} else {
    	    while (i.hasNext()) {
    	    	Entry<K,V> e = i.next();
    			if (value.equals(e.getKey())) return e;
    	    }
    	}
    	return null;
	}

	public K getKey(V value) {
    	Entry<V,K> e = getEntryByValue(value);
    	return (e == null) ? null : e.getValue();
	}

	public V getValue(K key) {
    	Entry<K,V> e = getEntryByKey(key);
    	return (e == null) ? null : e.getValue();
	}

	public K removeByValue(V value) {
		Entry<V,K> valEntry = getEntryByValue(value);
		Entry<K,V> keyEntry = getEntryByKey(valEntry.getValue());
		if (keyEntry != null) entrySetByKey.remove(keyEntry);
		if (valEntry != null) entrySetByValue.remove(valEntry);
		return keyEntry.getKey();
	}

	public V removeByKey(K key) {
		Entry<K,V> keyEntry = getEntryByKey(key);
		Entry<V,K> valEntry = getEntryByValue(keyEntry.getValue());
		if (keyEntry != null) entrySetByKey.remove(keyEntry);
		if (valEntry != null) entrySetByValue.remove(valEntry);
		return valEntry.getKey();
	}
	
    @SuppressWarnings("unchecked")
    @Override public V remove(Object key) {
    	return this.removeByKey((K)key);
    }


	@Override public V put(K key, V value) {
		Entry<K,V> keyEntry = getEntryByKey(key);
		Entry<V,K> valueEntry = getEntryByValue(value);
		
		V oldValue = null;
		
		if (keyEntry != null) { 
			Entry<V,K> inverseKeyEntry = getEntryByValue(keyEntry.getValue());
			keyEntry.getKey();
			entrySetByKey.remove(keyEntry);
			entrySetByValue.remove(inverseKeyEntry);
		} 
		if (valueEntry != null) {
			Entry<K,V> inverseValueEntry = getEntryByKey(valueEntry.getValue());
			oldValue = valueEntry.getKey();
			entrySetByValue.remove(valueEntry);
			entrySetByKey.remove(inverseValueEntry);
		} 
		entrySetByKey.add(new ReciprocalEntry<K,V>(key,value));
		entrySetByValue.add(new ReciprocalEntry<V,K>(value,key));
		
		return oldValue;
	}	
	
	static class ReciprocalEntry<K, V> implements Entry<K, V> {
		private K key;
		private V value;

		public ReciprocalEntry(K key, V value) {
			this.key = key;
			this.value = value;
		}

		public K getKey() { return key; }

		public V getValue() { return value; }

		public V setValue(V value) { 
			V oldValue = this.value;
			this.value = value;
			return oldValue;
		}

		public int hashCode() {
			return ((key == null) ? 0 : key.hashCode()) ^
				   ((value == null) ? 0 : value.hashCode());
		}

		@Override public String toString() {
			return "ReciprocalEntry[" + key + "=" + value + "]";
		}

		@SuppressWarnings("unchecked")
        @Override
        public boolean equals(Object o) {
			if (!(o instanceof Map.Entry)) return false;
			try {
				Map.Entry<K,V> e = (Map.Entry<K,V>)o;
				return safeEquals(key, e.getKey()) && safeEquals(value, e.getValue());
			} catch (ClassCastException cce) { 
				return false; 
			}
		}

		private static boolean safeEquals(Object o1, Object o2) {
			return (o1 == null ? o2 == null : o1.equals(o2));
		}
	}

}